package extractor;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import extractor.view.database.connect.Login;
import fx.FX;
import javafx.application.Application;
import javafx.stage.Stage;
import misc.Base;
import misc.Folder;
import misc.Title;
import mysql.Column;
import mysql.Database;
import mysql.Table;
import mysql.types.Types;
import node.java.JavaClass;
import node.java.JavaPackage;

import java.io.IOException;
import java.io.PrintWriter;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import static com.google.common.base.CaseFormat.LOWER_CAMEL;
import static com.google.common.base.CaseFormat.LOWER_UNDERSCORE;
import static mysql.Database.Notification.CONNECTED;
import static mysql.Database.Notification.REQUEST_CREDENTIALS;

/**
 * Created by thormentor on 27-04-2014.
 */
public class JavaGenerator extends Application{
    public static boolean RESET_CREDENTIALS = true;

    private Base base = new Base(this);

    @Override
    public void start(Stage stage) throws Exception {

        database = new Database("bamp_db");

        database.on(REQUEST_CREDENTIALS, () -> {
            base.info("Ask for credentials");
            FX.popup(new Login().setOnSignIn(database::setCredentials), 300, 275);
        });

        database.on(CONNECTED, () -> {
            try {
                database.extract();
                JavaPackage javaPackage = convert(database);

                for (JavaClass javaClass : javaPackage.getClasses()) {
                    try (PrintWriter writer = FOLDER.createPath(javaClass.getName(), "java").getWriter()) {
                        writer.print(javaClass);
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            } catch (ClassNotFoundException | SQLException e) {
                e.printStackTrace();
            }
        });

        database.go(RESET_CREDENTIALS);
    }

    private static final Folder FOLDER = new Folder("BAMP/src/bamp/entities");
    private static final List<String> EXCEPTIONS = Lists.newArrayList("created_at", "updated_at", "deleted_at");

    private Map<Table, JavaClass> generated = new LinkedHashMap<>();
    private Database database;

    public static void main(String... args) {
        launch(args);
    }

    public JavaPackage convert(Database database) throws SQLException {
        JavaPackage javaPackage = new JavaPackage();

        for(Table table : database.getTables())
            javaPackage.add(convert(table));

        return javaPackage;
    }

    private JavaClass convert(Table table) throws SQLException {
        Title title = Title.fromLowerUnderscore(table.getName()).singularize();
        JavaClass javaClass = new JavaClass(title);

        Config.Entity config = Config.MAP.get(javaClass.getName());
        if (config != null){
            if (config.annotation != null)
                javaClass.setAnnotation(config.annotation);

            if (config.fields != null && config.fields.size() > 0)
                javaClass.createFields(config.fields);

            if (config.methods != null && config.methods.size() > 0)
                javaClass.createMethods(config.methods);
        }

        int numDependencyTables = table.getDependencyTables().length;

        for(Column column : table.getColumns()) {

            if (EXCEPTIONS.contains(column.getName()))
                continue;

            Column.Reference reference = column.getReference();

            switch (numDependencyTables){
                case 0:
                    if (column.isPrimary())
                        continue;
                    break;
                case 1:
                    if (column.isPrimary()) {
                        try {
                            on(reference.getTable(), javaClass::setSuperClass);
                        }
                        catch (Exception e){
                            System.out.println(column.getName());
                            System.out.println(Joiner.on("\n").join(table.getDependencyTables()));
                            throw e;
                        }
                        continue;
                    }

            }

            if (reference != null){
                String fieldName;
                try {
                    fieldName = LOWER_UNDERSCORE.to(LOWER_CAMEL, column.getName().substring(0, column.getName().length() - 3));
                }
                catch (Exception e){
                    throw new RuntimeException(table.getName() + "." + column.getName(), e);
                }

                Table referencedTable = column.getReference().getTable();

                String annotation = config == null || config.fields == null ? null : config.fields.get(fieldName);
                on(referencedTable, created -> javaClass.createField(created, fieldName, annotation));

                continue;
            }

            Class type = Types.valueOf(column.getType());
            String fieldName = LOWER_UNDERSCORE.to(LOWER_CAMEL, column.getName());
            String annotation = config == null || config.fields == null ? null : config.fields.get(fieldName);
            javaClass.createField(type, fieldName, annotation);
        }

        trigger(table, javaClass);

        return javaClass;
    }

    private void trigger(Table table, JavaClass javaClass) {
        generated.put(table, javaClass);
        if (Listeners.containsKey(table))
            for(ClassCreationListener listener : Listeners.get(table))
                listener.created(javaClass);
    }

    private Map<Table, List<ClassCreationListener>> Listeners = new LinkedHashMap<>();

    private void on(Table table, ClassCreationListener listener){
        if (generated.containsKey(table)){
            listener.created(generated.get(table));
            return;
        }

        if (!Listeners.containsKey(table))
            Listeners.put(table, new ArrayList<>());

        Listeners.get(table).add(listener);
    }
}
